#!/usr/bin/env python2

import pcap
import dpkt
import dnet
import sys
import string
import socket
import socket
import re
import time
import Queue
import threading
import signal
import urllib

from threading import Thread
from optparse import OptionParser


# global variable
MALWARE_DNS = "https://nsgbot.googlecode.com/svn/wiki/Malware_DNS.wiki"
HONEYPOT_IP = "127.0.0.1"
dev = "eth0"
verbose = False

# make each Queue for thread
queue_dns = Queue.Queue()
SPOOFING_LIST = {"as-6rsuksbofu1v.local":"125.144.34.3",
"peer.pickeklosarske.ru":"125.144.34.3",
"haconnect.qq.com":"125.144.34.3",
"tracker.bittorrent.am":"125.144.34.3"}
DNS_Cache = {}

# SIGNAL
WORKING = True
LOCK = threading.Lock()
STOP = 0

class Signalled(Exception):
    pass

def sigBreak(signum, f):
    global STOP
    LOCK.acquire()
    STOP = 1
    LOCK.release()
    raise Signalled

def print_packet(pktlen, data, timestamp):
    if not data:
        return

    eth = dpkt.ethernet.Ethernet(data)
    # Check IP
    if eth.type == dpkt.ethernet.ETH_TYPE_IP:
        ip = eth.data
    else:
        return
    # Check UDP
    if ip.p == dpkt.ip.IP_PROTO_UDP:
        udp = ip.data
    else:
        return

    # Push to wanted Queue
    try:
        if udp.sport == 53 or udp.dport == 53:
            # Port based
            # DNS packet
            queue_dns.put(ip)
            return
    except:
        print "Exception: (%s)->(%s)" % (socket.inet_ntoa(ip.src), socket.inet_ntoa(ip.dst))
        return




class Packet_Parser(Thread):
    def run(self):
        print "Start Packet Parser.."
        p = pcap.pcapObject()
        #dev = sys.argv[1]
        debug("Open Network Interface: %s" % dev) 
        p.open_live(dev, 1600, 1, 100)
        
        while 1:
            LOCK.acquire()
            stop = STOP
            LOCK.release()
            if stop:
                print '%s' % sys.exc_type
                print 'shutting down'
                print '%d packets receive, %d packets dropped, %d packets dropped by interface' % p.stats()

                # send False to each queue for stop
                print "dns false"
                queue_dns.put(False)
                break

            p.dispatch(1, print_packet)                
        print "Finished Packet_Parser"

class Analyzer_DNS(Thread):
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue
        self.sock = dnet.ip()

    def run(self):
        print "Start DNS Analyzer"
        # DB must be inited in thread main

        # get first item 
        decoded = self.queue.get()
        # check exit signal
        if decoded == False: 
            print "exit signal in DNS"
            return

        while decoded:
            #decoded = self.queue.get()
            ip = decoded
            udp = ip.data
            try:
                dns = dpkt.dns.DNS(udp.data)
            except:
                print "Exception(DNS): (%s)->(%s)" % (socket.inet_ntoa(ip.src), socket.inet_ntoa(ip.dst))                
                decoded = self.queue.get()
                continue


            if dns.qr == dpkt.dns.DNS_Q:
                # TEST
                if self.checkDomain(dns.qd[0].name,["ru","cn"]) == True:
                    print "DNS:", dns.qd[0].name
                    #SPOOFING_LIST[dns.qd[0].name] = HONEYPOT_IP
                # Query
                if SPOOFING_LIST.has_key(dns.qd[0].name) == True:
                    self.reply_malware_dns(dns.qd[0].name,ip)
                    #print socket.inet_ntoa(ip.src),socket.inet_ntoa(ip.dst), dns.qd[0].name
            else:
                self.analyze_answer(ip)

            decoded = self.queue.get()
            if decoded == False: 
                print "Break signal from Queue(DNS)"
                break
        print "Finished DNS thread"

    def reply_malware_dns(self, domain, ip):
        # send spoofing packet for client
        # param: dns (request packet)
        dns = dpkt.dns.DNS(ip.data.data)
            # transform DNS Query into response
        dns.op = dpkt.dns.DNS_RA
        dns.rcode = dpkt.dns.DNS_RCODE_NOERR
        dns.qr = dpkt.dns.DNS_R
            # construct fake answer RR
        arr = dpkt.dns.DNS.RR()
        arr.cls = dpkt.dns.DNS_IN
        arr.name = domain
        arr.ip = dnet.addr(SPOOFING_LIST[domain]).ip
            
        dns.an.append(arr)
        
        udp = ip.data
        udp.sport, udp.dport = udp.dport, udp.sport
            # fix up IP and UDP layers
        ip.src, ip.dst = ip.dst, ip.src
        udp.data = dns
        udp.ulen = len(udp)
        ip.len = len(ip)

        # send out spoofed response
        buf = dnet.ip_checksum(str(ip))
        self.sock.send(buf)
        debug("### Send DNS SPOOFING ###: %s(%s)" % (socket.inet_ntoa(ip.dst), domain) )


    def analyze_answer(self, ip):
        # parse DNS answer packet

        msg = dpkt.dns.DNS(ip.data.data)

        if msg.qr == dpkt.dns.DNS_A and msg.rcode == dpkt.dns.DNS_RCODE_NOERR and len(msg.an)>0:
            queried = ""
            if msg.qd[0].type == dpkt.dns.DNS_A:
                queried = queried + "%s (A)" % (msg.qd[0].name)
            if msg.qd[0].type == dpkt.dns.DNS_NS:
                queried = queried + "%s (NS)" % (msg.qd[0].name)
            if msg.qd[0].type == dpkt.dns.DNS_CNAME:
                queried = queried + "%s (CNAME)" % (msg.qd[0].name)
            if msg.qd[0].type == dpkt.dns.DNS_SOA:
                queried = queried + "%s (SOA)" % (msg.qd[0].name)
            if msg.qd[0].type == dpkt.dns.DNS_PTR:
                queried = queried + "%s (PTR)" % (msg.qd[0].name)
            if msg.qd[0].type == dpkt.dns.DNS_HINFO:
                queried = queried + "%s (HINFO)" % (msg.qd[0].name)
            if msg.qd[0].type == dpkt.dns.DNS_MX:
                queried = queried + "%s (MX)" % (msg.qd[0].name)
            if msg.qd[0].type == dpkt.dns.DNS_TXT: 
                queried = queried + "%s (TXT)" % (msg.qd[0].name)
            if msg.qd[0].type == dpkt.dns.DNS_AAAA:            
                queried = queried + "%s (AAAA)" % (msg.qd[0].name)
            if msg.qd[0].type == dpkt.dns.DNS_SRV:
                queried = queried + "%s (SRV)" % (msg.qd[0].name)     

            answers = []
            dstip = ''
            for an in msg.an:
                if an.type == dpkt.dns.DNS_A:
                    answers.append(socket.inet_ntoa(an.ip))
                    dstip = socket.inet_ntoa(an.ip)
                elif an.type == dpkt.dns.DNS_PTR:
                    answers.append(an.ptrname)
                elif an.type == dpkt.dns.DNS_NS:
                    answers.append(an.nsname)
                elif an.type == dpkt.dns.DNS_CNAME:
                    answers.append(an.cname)
                elif an.type == dpkt.dns.DNS_SOA:
                    answers.append(an.mname)
                    answers.append(an.rname)
                    answers.append(str(an.serial))
                    answers.append(str(an.refresh))
                    answers.append(str(an.retry))
                    answers.append(str(an.expire))
                    answers.append(str(an.minimum)) 
                elif an.type == dpkt.dns.DNS_HINFO:
                    answers.append(" ".join(an.text))
                elif an.type == dpkt.dns.DNS_MX:
                    answers.append(an.mxname)
                elif an.type == dpkt.dns.DNS_TXT:
                    answers.append(" ".join(an.text)) 
                elif an.type == dpkt.dns.DNS_AAAA:
                    answers.append(socket.inet_ntop(socket.AF_INET6,an.ip6))
                elif an.type == dpkt.dns.DNS_SRV:
                    # do something with SRV
                    pass
                else:
                    # un-handled type
                    answers.append("[Honeysnap: Undecoded response]")
                    continue 
                

                for item in answers:
                    if item == "kdn.ktguide.com" or item == "127.0.0.1":
                        debug( "%s -> %s" % (queried, ",".join(answers)) )
                        SPOOFING_LIST[msg.qd[0].name] = HONEYPOT_IP
                    else:
                        if self.checkDomain(msg.qd[0].name, ["cn","ru"]) == True:
                            debug( "[%s] %s -> %s" % (socket.inet_ntoa(ip.dst), queried, ",".join(answers)) )
                            #SPOOFING_LIST[msg.qd[0].name] = HONEYPOT_IP
    def is_foreignCountry(self, qry):
        item = qry.split(".")
        last = item[-1].split(" ")
        domain = last[0]
        if domain == "ru" or domain == "cn":
            return True


    def checkDomain(self, qry, list):
        # Check qry domain is in list
        # qry: abc.com
        # list: ['com','ru','cn']
        item = qry.split(".")
        for dns in list:
            if item[-1] == dns:
                return True
        return False

class Console(Thread):
    def run(self):
        print "Start Console mode:"
        while STOP == 0:
            cmd = raw_input("Console(help:?) >")
            if cmd == "Q" or cmd == "exit" or cmd == "q":
                return
            index = cmd.split(" ")
            if index[0] == "dns":
                # add dns to SPOOFING_LIST
                self.add_dns(index[1])
            elif index[0] == "show":
                self.show_list()
            else:
                self.showHelp()
        
    def add_dns(self, dns):
        debug("Add dns:%s to %s" % (dns, HONEYPOT_IP))
        SPOOFING_LIST[dns] = HONEYPOT_IP

    def show_list(self):
        # show SPOOFING_LIST
        count = 0
        for key in SPOOFING_LIST.keys():
            count += 1
            print key
        print "="*40
        print " Total Malware DNS : ", count

    def showHelp(self):
        msg = """
Usage: cmd
   1) dns <domain> : add malware DNS to SPOOFING_LIST
   2) show : show SPOOFING_LIST
   3) ? : this menu
"""
        print msg

def startAnalyzer():
    parser = OptionParser()
    parser.add_option("-i", "--interface", dest="interface", help="Monitoring Network Interface", default="eth0")
    parser.add_option("-d", "--dest", dest="dest", help="Honeypot Server IP")
    parser.add_option("-w", "--www", action="store_true", dest="www", help="Online Malware DNS DB")
    parser.add_option("-v", "--verbose", dest="verbose", action="store_true")

    (options, args) = parser.parse_args()

    global dev
    global HONEYPOT_IP
    global verbose

    if options.verbose:
        verbose = True

    if options.interface:
        dev = options.interface
    if options.dest:
        HONEYPOT_IP = options.dest
    if options.www:
        initDB()


    #print blackList
    thr_dns = Analyzer_DNS(queue_dns)
    thr_par = Packet_Parser()
    thr_con = Console()

    thr_dns.start()
    thr_par.start()
    thr_con.start()
    try:
        signal.pause()
    except Signalled:
        print "* Singalled"

    print "queue join"

    print "finish queue join"
    thr_par.join()
    thr_dns.join()
    thr_con.join()

def debug(str):
    global verbose
    if verbose:
        print "[DEBUG] ", str

def initDB():
    fp = urllib.urlopen(MALWARE_DNS)
    for index in fp:
        temp = index.split("||")
        print temp[2], "-->", HONEYPOT_IP
        SPOOFING_LIST[temp[2]] = HONEYPOT_IP

if __name__ == "__main__":

    signal.signal(signal.SIGINT, sigBreak)
    # packet capture & analyze
    startAnalyzer()
